/*
* Copyright (C) Microsoft. All rights reserved.  
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not 
* use this file except in compliance with the License.  You may obtain a copy 
* of the License at http://www.apache.org/licenses/LICENSE-2.0  
* 
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED 
* WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
* MERCHANTABLITY OR NON-INFRINGEMENT. 
*
* See the Apache License, Version 2.0 for specific language governing 
* permissions and limitations under the License.
*
*/
#pragma once
#include <baja/common.hpp>
#include <baja/math.hpp>
#include <journal/models/interfaces.h>
#include <stack>
#include <journal/app/constants.h>

namespace journal { namespace view {

namespace m = baja::math;
namespace b = baja;

const double c_slowDownMs = 10; // artificial slowdown to see the branches being drawn
const char* const s_replacement = "ff-[-f+f+f]+[+f-f-f]";
const b::float32 c_maxAngle = 25;

class fractal
{
public:
    fractal(const std::shared_ptr<model::iink_surface_model>& inkSurface) : _inkSurface(inkSurface)
    {
        _direction = m::vector2d(0.f, 0.1f);
        _position = m::point2d(
            (constants::general::pageWorldDimensions().x/3) * b::rand(-1, 1),
            (constants::general::pageWorldDimensions().y/3) * b::rand(-1, 1)
            );
    }

    void generate(const char* axiom, const int level = 1)
    {
        for (const char * c = axiom; *c; c++)
        {
            const char* s = nullptr;
            if ((level < 5) && (s = replace(*c)))
            {
                generate(s, level+1);
            }
            else
            {
                execute(*c);
            }
        }
    }

private:
    const char* const replace(const char c)
    {
        if (c == 'f') 
        {
            return s_replacement; 
        } 
        else 
        { 
            return nullptr; 
        }
    }

    void execute(const char c)
    {
        if (c == 'f') // go forward
        {
            m::point2d newPosition;
            _position.offsetBy(_direction, &newPosition);

            std::shared_ptr<model::iink_stroke_model> stroke = _inkSurface->createInkStroke();
            stroke->setColor(bi::ink_color::black);
            stroke->setWidth(0.04f / (_stack.size() * _stack.size()));
            stroke->addPoint(_position, 0.5f, 0);
            stroke->addPoint(newPosition, 0.5f, 0);
            _inkSurface->attachInkStroke(stroke);
            
            _position = newPosition;
        }
        else if (c == '[')
        {
            stack_item si;
            si.direction = _direction;
            si.position = _position;
            _stack.push(si);
        }
        else if (c == ']')
        {
            stack_item si = _stack.top();
            _direction = si.direction;
            _position = si.position;
            _stack.pop();
        }
        else if (c == '+')
        {
            b::float32 angle = m::degToRad(c_maxAngle * b::rand(-1, 1));

            m::matrix33 rotation;
            rotation.m[0][0] = cos(angle);
            rotation.m[1][0] = -sin(angle);
            rotation.m[2][0] = 0;
            rotation.m[0][1] = sin(angle);
            rotation.m[1][1] = cos(angle);
            rotation.m[2][1] = 0;
            rotation.m[0][2] = 0;
            rotation.m[1][2] = 0;
            rotation.m[2][2] = 1;

            m::vector2d newDirection;
            rotation.apply(_direction, &newDirection);
            _direction = newDirection;
        }
    }

private:
    struct stack_item { m::vector2d direction; m::point2d position; };
    std::stack<stack_item> _stack;
    m::vector2d _direction;
    m::point2d _position;
    std::shared_ptr<model::iink_surface_model> _inkSurface;
};

}} // namespace journal::view